﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ClearingRestartingAutomaton.Common;

namespace ClearingRestartingAutomaton.Base
{
    /// <summary>
    /// The Automaton class represents the Clearing Restarting Automaton.
    /// </summary>
    [Serializable]
    public class Automaton
    {
        #region Constructors

        /// <summary>
        /// Default constructor of this class.
        /// </summary>
        public Automaton()
        {

        }

        /// <summary>
        /// Constructor of this class.
        /// </summary>
        /// <param name="instructions">The list of instructions.</param>
        public Automaton(List<Instruction> instructions)
        {
            this.instructions = instructions;
        }
        
        #endregion

        #region Public Properties

        /// <summary>
        /// The list of instructions used for computation.
        /// </summary>
        public List<Instruction> Instructions
        {
            get { return this.instructions; }
        }
        
        #endregion

        #region Public Methods

        /// <summary>
        /// Returns the InstructionStepCollection structure encapsulating the enumeration of all possible 
        /// InstructionStep structures that can be applied to the given input word in the specified context.
        /// </summary>
        /// <param name="word">The word to which the instructions are applied.</param>
        /// <param name="leftContext">The left context of the given input word.</param>
        /// <param name="rightContext">The right context of the given input word.</param>
        /// <param name="use">The way the instructions are used.</param>
        /// <returns>
        /// The InstructionStepCollection structure encapsulating the the enumeration of all possible
        /// InstructionStep structures that can be applied to the given input word in the specified context.
        /// </returns>
        public InstructionStepCollection MakeStep(
            string word,
            string leftContext,
            string rightContext,
            InstructionUse use)
        {
            List<InstructionStep> steps = new List<InstructionStep>();
            foreach (Instruction instruction in this.instructions)
            {
                steps.AddRange(instruction.MakeStep(word, leftContext, rightContext, use).Steps);
            }
            return new InstructionStepCollection(word, leftContext, rightContext, use, steps);
        }

        /// <summary>
        /// Returns the InstructionStepCollection structure encapsulating the enumeration of all possible 
        /// InstructionStep structures that can be applied to the given input word in the default context (^, $).
        /// </summary>
        /// <param name="word">The word to which the instructions are applied.</param>
        /// <param name="use">The way the instructions are used.</param>
        /// <returns>
        /// The InstructionStepCollection structure encapsulating the the enumeration of all possible InstructionStep 
        /// structures that can be applied to the given input word in the default context (^, $).
        /// </returns>
        public InstructionStepCollection MakeStep(string word, InstructionUse use)
        {
            return this.MakeStep(word, StringDefaultValues.LeftDelimiter, StringDefaultValues.RightDelimiter, use);
        }

        /// <summary>
        /// Returns the InstructionStepCollection structure encapsulating the enumeration of all possible 
        /// InstructionStep structures that can be used to reduce the given input word in the specified context.
        /// </summary>
        /// <param name="word">The word to which the instructions are applied.</param>
        /// <param name="leftContext">The left context of the given input word.</param>
        /// <param name="rightContext">The right context of the given input word.</param>
        /// <returns>
        /// The InstructionStepCollection structure encapsulating the the enumeration of all possible InstructionStep 
        /// structures that can be used to reduce the given input word in the specified context.
        /// </returns>
        public InstructionStepCollection ReduceStep(string word, string leftContext, string rightContext)
        {
            return this.MakeStep(word, leftContext, rightContext, InstructionUse.Reduction);
        }

        /// <summary>
        /// Returns the InstructionStepCollection structure encapsulating the enumeration of all possible 
        /// InstructionStep structures that can be used to reduce the given input word in the default context (^, $).
        /// </summary>
        /// <param name="word">The word to which the instructions are applied.</param>
        /// <returns>
        /// The InstructionStepCollection structure encapsulating the the enumeration of all possible 
        /// InstructionStep structures that can be used to reduce the given input word in the default context (^, $).
        /// </returns>
        public InstructionStepCollection ReduceStep(string word)
        {
            return this.MakeStep(word, StringDefaultValues.LeftDelimiter, StringDefaultValues.RightDelimiter, InstructionUse.Reduction);
        }

        /// <summary>
        /// Returns the InstructionStepCollection structure encapsulating the enumeration of all possible 
        /// InstructionStep structures that can be used to generate from the given input word in the specified context.
        /// </summary>
        /// <param name="word">The word to which the instructions are applied.</param>
        /// <param name="leftContext">The left context of the given input word.</param>
        /// <param name="rightContext">The right context of the given input word.</param>
        /// <returns>
        /// The InstructionStepCollection structure encapsulating the the enumeration of all possible InstructionStep 
        /// structures that can be used to generate from the given input word in the specified context.
        /// </returns>
        public InstructionStepCollection GenerateStep(string word, string leftContext, string rightContext)
        {
            return this.MakeStep(word, leftContext, rightContext, InstructionUse.Generation);
        }

        /// <summary>
        /// Returns the InstructionStepCollection structure encapsulating the enumeration of all possible 
        /// InstructionStep structures that can be used to generate from the given input word in the default context (^, $).
        /// </summary>
        /// <param name="word">The word to which the instructions are applied.</param>
        /// <returns>
        /// The InstructionStepCollection structure encapsulating the the enumeration of all possible InstructionStep 
        /// structures that can be used to generate from the given input word in the default context (^, $).
        /// </returns>
        public InstructionStepCollection GenerateStep(string word)
        {
            return this.MakeStep(word, StringDefaultValues.LeftDelimiter, StringDefaultValues.RightDelimiter, InstructionUse.Generation);
        }

        /// <summary>
        /// Returns the WordTree structure describing the rooted tree of words obtained from the given 
        /// initial word placed at the root of the tree by applying the given set of instructions.
        /// </summary>
        /// <param name="initialWord">The initial word placed at the root of the tree.</param>
        /// <param name="leftContext">The left context of the given initial word.</param>
        /// <param name="rightContext">The right context of the given initial word.</param>
        /// <param name="use">The way the instructions are used.</param>
        /// <param name="maxCount">The maximal number of words in the tree. This parameter is not used if set to zero.</param>
        /// <param name="maxLength">The maximal length of words in the tree. This parameter is not used if set to zero.</param>
        /// <returns>
        /// The WordTree structure describing the rooted tree of words obtained from the given 
        /// initial word placed at the root of the tree by applying the given set of instructions.
        /// </returns>
        public WordTree MakeWordTree(
            string initialWord,
            string leftContext,
            string rightContext,
            InstructionUse use,
            int maxCount,
            int maxLength)
        {
            List<string> treeList = new List<string>();
            SortedDictionary<string, bool> treeSet = 
                new SortedDictionary<string, bool>(new StringLengthLexComparer());
            SortedDictionary<string, InstructionStep> treeSteps = 
                new SortedDictionary<string, InstructionStep>(new StringLengthLexComparer());
            Queue<string> Q = new Queue<string>();
            treeSet[initialWord] = true;
            treeList.Add(initialWord);
            Q.Enqueue(initialWord);
            while (Q.Count > 0 && (maxCount == 0 || treeList.Count < maxCount))
            {
                string actualWord = Q.Dequeue();
                InstructionStepCollection instructionStepCollection = MakeStep(actualWord, leftContext, rightContext, use);
                foreach (InstructionStep step in instructionStepCollection.Steps)
                {
                    if ((maxCount == 0 || treeList.Count < maxCount) &&
                        (maxLength == 0 || step.ResultWord.Length <= maxLength) &&
                        (!treeSet.ContainsKey(step.ResultWord)))
                    {
                        treeSet[step.ResultWord] = true;
                        treeList.Add(step.ResultWord);
                        treeSteps[step.ResultWord] = step;
                        Q.Enqueue(step.ResultWord);
                    }
                }
            }
            treeSet[initialWord] = false;
            return new WordTree(initialWord, leftContext, rightContext, use, treeList, treeSet, treeSteps);
        }

        /// <summary>
        /// Returns the WordTree structure describing the rooted tree of words obtained from the given 
        /// initial list of words by applying the given set of instructions.
        /// </summary>
        /// <param name="initialWords">The initial list of words.</param>
        /// <param name="leftContext">The left context of the given initial list of words.</param>
        /// <param name="rightContext">The right context of the given initial list of words.</param>
        /// <param name="use">The way the instructions are used.</param>
        /// <param name="maxCount">The maximal number of words in the tree. This parameter is not used if set to zero.</param>
        /// <param name="maxLength">The maximal length of words in the tree. This parameter is not used if set to zero.</param>
        /// <returns>
        /// The WordForest structure describing the rooted tree of words obtained from the given 
        /// initial list of words by applying the given set of instructions.
        /// </returns>
        public WordForest MakeWordForest(
            List<string> initialWords,
            string leftContext,
            string rightContext,
            InstructionUse use,
            int maxCount,
            int maxLength)
        {
            List<string> forestList = new List<string>();
            SortedDictionary<string, int> forestSet = 
                new SortedDictionary<string, int>(new StringLengthLexComparer());
            SortedDictionary<string, List<InstructionStep>> forestSteps = 
                new SortedDictionary<string, List<InstructionStep>>(new StringLengthLexComparer());
            Queue<string> Q = new Queue<string>();
            foreach (string initialWord in initialWords)
            {
                if (!forestSet.ContainsKey(initialWord) && (maxCount == 0 || forestList.Count < maxCount))
                {
                    forestSet[initialWord] = 0;
                    forestList.Add(initialWord);
                    Q.Enqueue(initialWord);
                }
            }
            while (Q.Count > 0 && (maxCount == 0 || forestList.Count < maxCount))
            {
                string actualWord = Q.Dequeue();
                InstructionStepCollection instructionStepCollection = MakeStep(actualWord, leftContext, rightContext, use);
                if (instructionStepCollection.Steps.Count == 0)
                {
                    forestSet[actualWord] = 2;
                }
                foreach (InstructionStep step in instructionStepCollection.Steps)
                {
                    if ((maxCount == 0 || forestList.Count < maxCount) &&
                        (maxLength == 0 || step.ResultWord.Length <= maxLength))
                    {
                        if (!forestSet.ContainsKey(step.ResultWord))
                        {
                            forestSet[step.ResultWord] = 0;
                            forestList.Add(step.ResultWord);
                            Q.Enqueue(step.ResultWord);
                        }
                        if (!forestSteps.ContainsKey(step.ResultWord))
                        {
                            forestSteps[step.ResultWord] = new List<InstructionStep>();
                            forestSteps[step.ResultWord].Add(step);
                        }
                        else if (!forestSteps[step.ResultWord].Any(s => s.Word.Equals(actualWord)))
                        {
                            forestSteps[step.ResultWord].Add(step);
                        }
                    }
                }
            }
            return new WordForest(initialWords, leftContext, rightContext, use, forestList, forestSet, forestSteps);
        }

        /// <summary>
        /// Returns the WordTree structure describing the rooted tree of words reduced from the given 
        /// initial word placed at the root of the tree by using instructions from the given set of instructions.
        /// </summary>
        /// <param name="initialWord">The initial word placed at the root of the tree.</param>
        /// <param name="leftContext">The left context of the given initial word.</param>
        /// <param name="rightContext">The right context of the given initial word.</param>
        /// <param name="maxCount">The maximal number of words in the tree. This parameter is not used if set to zero.</param>
        /// <param name="maxLength">The maximal length of words in the tree. This parameter is not used if set to zero.</param>
        /// <returns>
        /// The WordTree structure describing the rooted tree of words reduced from the given 
        /// initial word placed at the root of the tree by using instructions from the given set of instructions.
        /// </returns>
        public WordTree Reduce(string initialWord, string leftContext, string rightContext, int maxCount, int maxLength)
        {
            return this.MakeWordTree(initialWord, leftContext, rightContext, InstructionUse.Reduction, maxCount, maxLength);
        }

        /// <summary>
        /// Returns the WordTree structure describing the rooted tree of words generated from the given 
        /// initial word placed at the root of the tree by using instructions from the given set of instructions.
        /// </summary>
        /// <param name="initialWord">The initial word placed at the root of the tree.</param>
        /// <param name="leftContext">The left context of the given initial word.</param>
        /// <param name="rightContext">The right context of the given initial word.</param>
        /// <param name="maxCount">The maximal number of words in the tree. This parameter is not used if set to zero.</param>
        /// <param name="maxLength">The maximal length of words in the tree. This parameter is not used if set to zero.</param>
        /// <returns>
        /// The WordTree structure describing the rooted tree of words generated from the given 
        /// initial word placed at the root of the tree by using instructions from the given set of instructions.
        /// </returns>
        public WordTree Generate(string initialWord, string leftContext, string rightContext, int maxCount, int maxLength)
        {
            return this.MakeWordTree(initialWord, leftContext, rightContext, InstructionUse.Generation, maxCount, maxLength);
        }

        /// <summary>
        /// Simplifies the automaton, i.e. removes the redundant instructions.
        /// </summary>
        public void Simplify()
        {
            List<Instruction> originalInstructions = new List<Instruction>(this.instructions);
            originalInstructions.Sort();
            this.instructions.Clear();
            foreach (Instruction instruction in originalInstructions)
            {
                WordTree instructionClosure = this.MakeWordTree(
                    instruction.FromWord,
                    instruction.LeftContext,
                    instruction.RightContext,
                    InstructionUse.Reduction, 0, 0);
                if (!instructionClosure.TreeSet.ContainsKey(instruction.ToWord))
                {
                    this.instructions.Add(instruction);
                }
            }
        }

        #endregion

        /// <summary>
        /// Returns a System.String that represents this instance.
        /// </summary>
        /// <returns>A System.String that represents this instance.</returns>
        public override string ToString()
        {
            StringBuilder stringBuilder = new StringBuilder();
            foreach (Instruction instruction in this.instructions)
                stringBuilder.AppendLine(instruction.ToString());
            return stringBuilder.ToString();
        }

        #region Private Data Members

        private List<Instruction> instructions = new List<Instruction>();

        #endregion
    }
}
